//
// catGL - Cheap Action-game Tool with WebGL.
//
// Copyright 2010, tmasda.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function ShaderManager() {};

ShaderManager.prototype = {
    // vertex shaderとfragment shaderの読み込みとlocationの取得
    loadShader: function(shaderName) {
        var gl = catGL.gl;
        var program = gl.createProgram();
        gl.attachShader(program, this.initShader(gl, shaderName, "v"));
        gl.attachShader(program, this.initShader(gl, shaderName, "f"));
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }
        gl.useProgram(program);

        // locationの取得（名前が固定のため共通化可能）
        // shader内で使われていない場合には-1が設定される
        program.locAttrVertex = gl.getAttribLocation(program, "aVertexPosition");
        program.locAttrColor = gl.getAttribLocation(program, "aVertexColor");
        program.locAttrNorm = gl.getAttribLocation(program, "aVertexNorm");
        program.locAttrTexCoord = gl.getAttribLocation(program, "aTextureCoord");

        // shader内で使われていない場合にはundefinedとなる
        program.locUnifPmatrix = gl.getUniformLocation(program, "uPMatrix");
        program.locUnifMVmatrix = gl.getUniformLocation(program, "uMVMatrix");
        program.locUnifNmatrix = gl.getUniformLocation(program, "uNMatrix");
        program.locUnifAmbLightColor = gl.getUniformLocation(program, "uAmbLightColor");
        program.locUnifDirLightDiffColor = gl.getUniformLocation(program, "uDirLightDiffColor");
        program.locUnifDirLightSpecColor = gl.getUniformLocation(program, "uDirLightSpecColor");
        program.locUnifDirLightVect = gl.getUniformLocation(program, "uDirLightVect");
        program.locUnifMatDiffuse = gl.getUniformLocation(program, "uMatDiffuse");
        program.locUnifMatSpecular = gl.getUniformLocation(program, "uMatSpecular");
        
        this.table[shaderName] = program;
        this.program = program;
    },

    initShader: function(gl, shaderName, kind) {
        var file = shaderName + "." + kind;
        var str = catGL.load_sync(catGL.lib.shaderPath + file).responseText;

        var shader;
        if (kind == "f"){
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (kind == "v"){
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, str);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(file + ":" + gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    },

    // 描画時に行うshaderのセットアップ
    setup: function(model) {
        var gl = catGL.gl;
        var shaderName = model.shaderName;
        var program = this.table[shaderName];
        
        // 利用するprogram(shaderの設定)
        gl.useProgram(program);

        // perspective matrixの設定
        gl.uniformMatrix4fv(program.locUnifPmatrix, false, catGL.camera.pMatrix);
        // model view matrixの設定
        var curMatrix = catGL.matrixStack.top(); // matrixのstackから一番上を取り出す
        gl.uniformMatrix4fv(program.locUnifMVmatrix, false, curMatrix);
        
        // 法線変換用のmatrixの作成と設定
        if (program.locUnifNmatrix){
            // model view matrixの逆行列の転置を求める
            var normMatrix = mat4.create();
            mat4.inverse(curMatrix, normMatrix);
            mat4.transpose(normMatrix);
            gl.uniformMatrix4fv(program.locUnifNmatrix, false, normMatrix);
        }

        if (program.locUnifMatDiffuse) {
            gl.uniform4fv(program.locUnifMatDiffuse, model.diffuses);
        }
        if (program.locUnifMatSpecular) {
            gl.uniform4fv(program.locUnifMatSpecular, model.speculars);
        }

        // vertex bufferの設定
        gl.bindBuffer(gl.ARRAY_BUFFER, model.vertexBuffer);
        gl.vertexAttribPointer(program.locAttrVertex, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(program.locAttrVertex);

        // vertex colorの設定
        if (program.locAttrColor != -1){
            gl.bindBuffer(gl.ARRAY_BUFFER, model.colorBuffer);
            gl.vertexAttribPointer(program.locAttrColor, 4, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.locAttrColor);
        }

        // 頂点法線の設定
        if (program.locAttrNorm != -1){
            gl.bindBuffer(gl.ARRAY_BUFFER, model.normBuffer);
            gl.vertexAttribPointer(program.locAttrNorm, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.locAttrNorm);
        }

        // texture 座標の設定
        if (program.locAttrTexCoord != -1){
            gl.bindBuffer(gl.ARRAY_BUFFER, model.texCoordBuffer);
            gl.vertexAttribPointer(program.locAttrTexCoord, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(program.locAttrTexCoord);
        }

        // textureの設定
        if (model.tex){
            gl.bindTexture(gl.TEXTURE_2D, model.tex);
        }

        // index bufferの設定
        if (model.indexBuffer){
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.indexBuffer);
        }
    },
    loadLightParam: function(){
        var gl = catGL.gl;
        // カメラの行列はゆがみとスケールが掛からないと仮定する
        //var curMatrix = catGL.matrixStack.top();
        var curMatrix = mat3.create();
        var lightManager = catGL.lightManager;
        var ambColor = lightManager.ambLightColor;
        var dirColor = lightManager.dirLightDiffColor;
        var specColor = lightManager.dirLightSpecColor;
        var dirVect = vec3.create();

        mat4.toInverseMat3(catGL.matrixStack.top(), curMatrix);
        mat3.transpose(curMatrix);
        mat3.multiplyVec3(curMatrix, lightManager.dirLightVect, dirVect);

        var shaderName;
        for (shaderName in this.table){
            var program = this.table[shaderName];
            gl.useProgram(program);
            if (program.locUnifAmbLightColor){
                gl.uniform3fv(program.locUnifAmbLightColor,  ambColor);
            }
            if (program.locUnifDirLightDiffColor){
                gl.uniform3fv(program.locUnifDirLightDiffColor, dirColor);
            }
            if (program.locUnifDirLightSpecColor){
                gl.uniform3fv(program.locUnifDirLightSpecColor, specColor);
            }
            if (program.locUnifDirLightVect){
                gl.uniform3fv(program.locUnifDirLightVect, dirVect);
            }
        }
    }

}


ShaderManager.create = function(){
    var sman = new ShaderManager();
    sman.table = [];
    return sman;
}
